﻿//---------------------------------------------------------------------
//Copyright (c) 2009, Pierre Milet
//All rights reserved.
//
//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
//INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
//IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 
//OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
//PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
//TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
//---------------------------------------------------------------------
using System;
using System.Collections;
using System.Runtime.InteropServices;
using System.Web;
using System.IO;
using System.Threading;
using System.Diagnostics;

using Microsoft.BizTalk.TransportProxy.Interop;
using Microsoft.BizTalk.Component.Interop;
using Microsoft.BizTalk.Message.Interop;
using Microsoft.Samples.BizTalk.Adapter.Common;
using Microsoft.XLANGs.BaseTypes;
using System.Xml;
using System.Text;
//using BizMock.Framework;


namespace BizMock.Adapter.Runtime
{
    /// <summary>
    /// Main class for BizMock receive adapters. It provides the implementations of
    /// core interfaces needed to comply with receiver adapter contract.
    /// (1) This class is actually a Singleton. That is there will only ever be one
    /// instance of it created however many locations of this type are actually defined.
    /// (2) Individual locations are identified by a URI and are associated with BizMockReceiveEndpoint
    /// (3) It is legal to have messages from different locations submitted in a single
    /// batch and this may be an important optimization. And this is fundamentally why
    /// the Receiver is a singleton.
    /// </summary>
    sealed public class BizMockReceiver : Receiver
    {
        private IBTTransportProxy transportProxy = null;
        private IBaseMessageFactory msgFactory = null;

        private const string protocol = "BizMock";
        private const string description = "receives messages from BizMock test environment and replies with responses";
        private static readonly Guid clsid = new Guid("fa0588ed-3353-4b70-9d04-f001c23b9f17");
        private ControlledTermination terminator = new ControlledTermination();
        private const int IO_BUFFER_SIZE = 8192;  // 8K
        private bool adapterRegistered = false;
        private BizMockResponseHandler _responseHandler = null;
        private SyncReceiveSubmitBatch _batch = null;
        private BizMockReceiverEndpoint _ep = null;
        private static readonly PropertyBase InboundTransportLocationProperty = new BTS.InboundTransportLocation();
        private static readonly PropertyBase InboundTransportTypeProperty = new BTS.InboundTransportType();
        private static readonly PropertyBase LoopBackProperty = new BTS.LoopBack();
        private static readonly PropertyBase WindowsUserProperty = new BTS.WindowsUser();
        private string _bizMockFileInterchangePath = string.Format("{0}\\{1}", System.Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), @"BizMock");


        public BizMockReceiver()
            : base(protocol, "1.0", description, protocol, clsid, null, typeof(BizMockReceiverEndpoint))
        {
        }

        private struct Request
        {
            public string uri;
            public Message message;
        }

        // Request processing method...
        public void ProcessRequest(string uri, Message request)
        {
            Init(uri.ToLower());
            try
            {
                Debug.WriteLine(string.Format("BizMockReceiveAdapter.ProcessRequest( uri:{0} ) called", uri), "BizMockReceive: Info");

                _ep = (BizMockReceiverEndpoint)base.GetEndpoint(uri);

                if (_ep == null)
                    throw new BizMockReceiveAdapterException(string.Format("Failed to get bizmock endpoint {0}", uri));

                request.PromoteProperty(InboundTransportLocationProperty.Name.Name, InboundTransportLocationProperty.Name.Namespace, uri, true);
                request.PromoteProperty(InboundTransportTypeProperty.Name.Name, InboundTransportTypeProperty.Name.Namespace, protocol, true);

                // Determine if the port is one or two and handle accordingly...
                if (_ep.Configuration.IsTwoWay)
                {
                    ProcessTwoWayRequest(uri, request);
                }
                else
                {
                    ProcessOneWayRequest(request);
                }
                Debug.WriteLine(string.Format("BizMockReceiveAdapter.ProcessRequest( uri:{0} ) finished", uri), "BizMockReceive: Info");
            }
            catch (Exception ex)
            {
                Debug.WriteLine(string.Format("BizMockReceiveAdapter.ProcessRequest( uri:{0} ) error {1} ", uri, ex.Message), "BizMockReceive: Info");
                this.TransportProxy.TerminateIsolatedReceiver();
                this.adapterRegistered = false;
                throw;
            }
        }

        public void ProcessResponse(string uri, Message response)
        {
            IBaseMessage message = response.CreateRequestMessage(this.msgFactory);
            MessageFileHelper.SaveMessage(message, _bizMockFileInterchangePath, uri, Guid.NewGuid().ToString(), "Responsectx", "Responsemsg", "Responsectrl");

        }

        public void GetResponse(string uri)
        {
            Exception transmitException = null;

            try
            {


                // It's a good idea to have a timeout on the response message
                IBaseMessage responseMsg = _responseHandler.WaitForResponse(_ep.Configuration.Timeout);
                if (responseMsg == null)
                {
                    // Response did not arrive in time!
                    throw new BizMockReceiveAdapterException("Failed to receive the BizMock response");
                }

                Stream btsStream = responseMsg.BodyPart.GetOriginalDataStream();
                btsStream.Position = 0;

                string portName = uri.ToLower();
                string directoryPath = string.Format("{0}\\{1}", System.Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), @"BizMock");

                for (var i = 0; i < responseMsg.PartCount; i++)
                {
                    string partName;
                    var part = responseMsg.GetPartByIndex(i, out partName);
                    part.GetOriginalDataStream().Position = 0;
                }

                MessageFileHelper.SaveMessage(responseMsg, directoryPath, portName, Guid.NewGuid().ToString(), "Responsectx", "Responsemsg", "Responsectrl");

            }
            catch (Exception ex)
            {
                transmitException = ex;
            }
            finally
            {
                // After processing the response message successfully, it MUST be deleted
                // from BizTalk message box (a.k.a. Application Queue). Otherwise, the message 
                // remains in the queue forever causing the system to become unresponsive. 
                _responseHandler.ResponseConsumed(transmitException);
                _batch.Dispose();
                Term();
            }
        }

        private void Init(string url)
        {
            if (false != this.adapterRegistered) return;
            // Since we are an Isolated Adapter, we need to create
            // this Adapters Transport Proxy and initialize it with
            // one of our urls...
            this.transportProxy = (IBTTransportProxy)new BTTransportProxy();
            base.Initialize(this.transportProxy);
            this.transportProxy.RegisterIsolatedReceiver(url, (IBTTransportConfig)this);
            this.msgFactory = this.transportProxy.GetMessageFactory();
            this.adapterRegistered = true;
        }

        private void Term()
        {
            if (false == this.adapterRegistered)
                return;

            lock (this)
            {
                if (!this.adapterRegistered) return;
                this.terminator.Terminate();

                // Inform this adapter's Transport Proxy that this
                // Adapter is shutting down...
                this.transportProxy.TerminateIsolatedReceiver();
                this.adapterRegistered = false;
            }
        }

        private void ProcessOneWayRequest(Message request)
        {
            // Create a new message...
            IBaseMessage msg = request.CreateRequestMessage(this.msgFactory);

            // Submit the message using the StandardReceiveBatchHandler
            SyncReceiveSubmitBatch batch = new SyncReceiveSubmitBatch(transportProxy, terminator, 1);
            try
            {
                batch.SubmitMessage(msg, null);
                batch.Done();

                if (!batch.Wait())
                {
                    string error = "BizMock ReceiveAdapter error when processing the request:";
                    foreach (FailedMessage fm in batch.FailedMessages)
                    {
                        if (fm.Message != null && fm.Message.GetErrorInfo() != null)
                            error += fm.Message.GetErrorInfo().Message + " | ";
                    }
                    throw new BizMockReceiveAdapterException(error);
                }
            }
            finally
            {
                batch.Dispose();
                if (Marshal.IsComObject(batch))
                {
                    while (0 < Marshal.ReleaseComObject(batch))
                        GC.SuppressFinalize(batch);
                }
                Term();
            }
        }

        private void ProcessTwoWayRequest(string uri, Message request)
        {
            // Create a new message...
            IBaseMessage msg = request.CreateRequestMessage(msgFactory);

            // Use the request-response handler to send the request message
            _batch = new SyncReceiveSubmitBatch(transportProxy, terminator, 1);
            //try
            //{
            DateTime expDateTime = DateTime.Now.AddSeconds(_ep.Configuration.Timeout);
            _responseHandler = new BizMockResponseHandler(transportProxy);
            _batch.SubmitRequestMessage(msg, Guid.NewGuid().ToString("D"), true, expDateTime, _responseHandler);

            _batch.Done();

            if (!_batch.Wait())
            {
                throw new BizMockReceiveAdapterException("Failed to submit the BizMock request");
            }
            //}
            //finally
            //{
            //    _batch.Dispose();
            //    if (Marshal.IsComObject(_batch))
            //    {
            //        while (0 < Marshal.ReleaseComObject(_batch))
            //            GC.SuppressFinalize(_batch);
            //    }
            //}
        }

        ~BizMockReceiver()
        {
            if (_batch != null)
            {
                _batch.Dispose();
                if (Marshal.IsComObject(_batch))
                {
                    while (0 < Marshal.ReleaseComObject(_batch))
                        GC.SuppressFinalize(_batch);
                }
            }
            Term();
        }
    }
}